const path = require('path');
const fse = require('fs-extra');
// FIXME: [log4js日志管理插件](http://blog.fens.me/nodejs-log4js/)
const morgan = require('morgan');
const FileStreamRotator = require('file-stream-rotator');
// FIXME: [winston日志管理插件]
// const winston = require('winston');
// const dayjs = require('dayjs');

const { sequelize } = require('../dao/sequelize');
const Log = require('../dao/models/sql-log')(sequelize);

/**
 * @title morganMiddleware
 * @url [express 默认日志组件 morgan 博客](https://segmentfault.com/a/1190000007769095)
 * @date [morgan日志模块的三种日期格式clf/web/iso](https://blog.csdn.net/palet/article/details/86661953)
 * @param {*} app Express实例
 * NOTICE: # 日志的输出流配置 [stdout|file|database] => LOG_STREAM_CONFIG = database
 */
module.exports = app => {
  /**
   * NOTICE: format是由token字段组合而成的
   */
  // 自定义token(先注册的后执行)
  morgan.token('user', (req, res) => {
    const { user } = res.locals || {};
    const { _id: userID } = user || {};
    delete res.locals;
    return userID || '-';
  });
  morgan.token('message', (req, res) => {
    // console.log('res.locals: ', res.locals);
    const { message } = res.locals || {};
    return message || '-';
  });
  // 自定义format，其中包含自定义的token
  morgan.format('joke', ':method  :url  HTTP/:http-version  :status  :message  :remote-addr  [:date[iso]]  :user  ":user-agent"  :res[content-length]  ":referrer"');

  /**
   * @title 注册日志中间件
   * @api morgan(format, options)
   * @param {string} options.stream 日志的输出流配置，默认是process.stdout。
   * @param {Function} options.skip 是否跳过日志记录，使用方式可以参考[这里](https://github.com/expressjs/morgan/#skip); 例:`skip: function (req, res) { return res.statusCode < 400 }`。
   * @param {boolean} options.immediate 默认是false。当为true时，一收到请求，就记录日志；如果为false，则在请求返回后，再记录日志。
   * NOTICE: options.immediate设置false; 等请求返回后得到res.locals中的信息
   */
  let logStream = null;
  const logDirectory = path.join(__dirname, '..', 'logs');
  switch (process.env.LOG_STREAM_CONFIG) {
    case 'stdout':
      app.use(morgan('joke'));
      break;
    case 'file':
      // 将日志打印到本地文件
      // logStream = fse.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' });

      // 日志切割
      // ensure log directory exists
      if (!fse.existsSync(logDirectory)) fse.mkdirSync(logDirectory);
      logStream = FileStreamRotator.getStream({
        date_format: 'YYYYMMDD',
        filename: path.join(logDirectory, 'access-%DATE%.log'),
        frequency: 'daily',
        verbose: false,
      });
      // logStream = { write: message => winston.info(message) };
      app.use(morgan('joke', { stream: logStream }));
      break;
    case 'database':
      /**
       * // opts为配置文件
        ```var stream = opts.stream || process.stdout;
        var logString = createLogString(); // 伪代码，根据format、token的定义，生成日志
        stream.write(logString);```
       * 通过源码,声明一个带write方法的对象，并作为stream配置传入
       */
      logStream = {
        write: line => {
          // 保存到数据库
          saveToDatabase(line);
        },
      };
      app.use(morgan('joke', { stream: logStream }));
      break;
    default:
      break;
  }
};

async function saveToDatabase(line) {
  const aLogLine = line.trim().split('  ');
  const aAttributes = ['method', 'url', 'http_version', 'status', 'message', 'remote_addr', 'visit_time', 'user', 'user_agent', 'content_length', 'referrer'];
  const oLog = {};
  aAttributes.forEach((item, index) => {
    const sAttribute = aLogLine[index];
    if (item === 'status' || item === 'content_length') {
      oLog[item] = parseInt(sAttribute, 10);
    } else if (item === 'visit_time') {
      /**
      * @description 正则匹配以[开头以]结尾不包含[]: str.match(/(?<=\[).*?(?=\])/g)
      * @description 正则匹配以[开头以]结尾包含[]: str.match(/\[.*?\]/g)
      * @description 正则匹配 {} 以及大括号 中间的内容: str.match(/\{[^\{]+\}/g)
      */
      const [dealDate] = sAttribute.match(/(?<=\[).*?(?=\])/gi);
      // oLog[item] = dayjs(dealDate).format('YYYY-MM-DD HH:mm:ss');
      oLog[item] = new Date(dealDate);
    } else {
      oLog[item] = sAttribute;
    }
  });
  try {
    await Log.create(oLog);
  } catch (error) {
    console.log('morgan', error);
  }
}
/*
 * [日志格式: format](https://github.com/expressjs/morgan/#predefined-formats)
{
  combined: {
    name: `:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"`,
    example: `::ffff:127.0.0.1 - - [04/Mar/2021:08:09:33 +0000] "PUT /users HTTP/1.1" 401 46 "-" "vscode-restclient"`,
  },
  common: {
    name: `:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]`,
    example: `::ffff:127.0.0.1 - - [04/Mar/2021:08:12:27 +0000] "PUT /users HTTP/1.1" 401 46`,
  },
  dev: {
    name: `:method :url :status :response-time ms - :res[content-length]`,
    example: `POST /users/login 200 70.312 ms - 320`,
  },
  short: {
    name: `:remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms`,
    example: `::ffff:127.0.0.1 - PUT /users HTTP/1.1 401 46 - 15.447 ms`,
  },
  tiny: {
    name: `:method :url :status :res[content-length] - :response-time ms`,
    example: `PUT /users 401 46 - 15.216 ms`,
  },
}
*/
